import { compiler } from './compiler'
import { IndexGenerator } from './generator/IndexGenerator'
import { LessGenerator } from './generator/LessGenerator'
import { ReactRuntimeGenerator } from './generator/ReactRuntimeGenerator'
import { SvgRuntimeGenerator } from './generator/SvgRuntimeGenerator'
import { VueRuntimeGenerator } from './generator/VueRuntimeGenerator'
import { VueNextRuntimeGenerator } from './generator/VueNextRuntimeGenerator'
import { pascalCase } from './util'

import { DynamicColorTransformer } from './transformer/DynamicColorTransformer'
import { RemoveConditionTransformer } from './transformer/RemoveConditionTransformer'
import { RemoveTagsTransformer } from './transformer/RemoveTagsTransformer'
import { RemovePropsTransformer } from './transformer/RemovePropsTransformer'
import { RemoveDataAttrTransformer } from './transformer/RemoveDataAttrTransformer'
import { DynamicSizeTransformer } from './transformer/DynamicSizeTransformer'
import { DynamicStrokeTransformer } from './transformer/DynamicStrokeTransformer'
import { UniqueIdTransformer } from './transformer/UniqueIdTransformer'
import { UniqueKeyframesIdTransformer } from './transformer/UniqueKeyframesIdTransformer'
import { RemoveCSSPrefixTransformer } from './transformer/RemoveCSSPrefixTransformer'
import { CamelTransformer } from './transformer/CamelTransformer'
import { DynamicHueTransformer } from './transformer/DynamicHueTransformer'
import { SyncGroupProps } from './transformer/SyncGroupProps'
import { ITransformPlugin } from './Transformer'

import {
  IIconColorHueInfo,
  IIconColorReplaceInfo,
  IRuntimeGeneratorOptions,
  IRuntimeOptions,
} from './generator/RuntimeGenerator'
import { IconGenerator } from './generator/IconGenerator'
import { JSXGenerator } from './generator/JSXGenerator'
import { SvgGenerator } from './generator/SvgGenerator'
import { FixMaskTypeTransformer } from './transformer/FixMaskTypeTransformer'

export interface IIconToolsOptions extends IRuntimeOptions {
  author: string
  type: 'react' | 'vue' | 'svg' | 'vue-next'
}

export type IconCompilerFunc = (info: IIconInfo) => string

export interface IIconFile {
  path: string
  mime: string
  content: string
}

export interface IIconInfo {
  name: string
  content: string
  description?: string
  rtl?: boolean
}

export class IconCompiler {
  public static instance(options: IIconToolsOptions): IconCompiler {
    return new IconCompiler(options)
  }

  private readonly options: IIconToolsOptions

  private readonly map: Record<string, string> = {}

  private readonly compiler: IconCompilerFunc

  private readonly runtimeCode: string

  private constructor(options: IIconToolsOptions) {
    this.options = options
    this.compiler = this.createCompiler()
    this.runtimeCode = this.createRuntimeCode()
  }

  public appendIcon(info: IIconInfo): void {
    this.map[info.name] = this.compiler(info)
  }

  public getIconCode(name: string): string {
    const svg = this.map[name]

    if (!this.map[name]) {
      throw new Error('call `this.appendIcon(' + name + ')` first')
    }

    return svg
  }

  public getIconFile(name: string): IIconFile {
    const svg = this.getIconCode(name)
    const { type, useType } = this.options
    const isSvg = type === 'svg'

    return {
      mime: 'image/svg+xml',
      path: `icons/${pascalCase(name)}.${useType ? 'ts' : 'js'}${
        isSvg ? '' : 'x'
      }`,
      content: svg,
    }
  }

  public getIconFiles(): IIconFile[] {
    const list = Object.keys(this.map).map((key) => this.getIconFile(key))

    list.push(this.getRuntimeFile(), this.getIndexFile())

    if (this.options.type !== 'svg') {
      list.push(this.getLessFile())
    }

    return list
  }

  public getRuntimeCode(): string {
    return this.runtimeCode
  }

  public getRuntimeFile(): IIconFile {
    return {
      mime: 'text/javascript',
      path: `runtime/index.${this.options.useType ? 'ts' : 'js'}x`,
      content: this.getRuntimeCode(),
    }
  }

  public getLessCode(): string {
    const { author, prefix = 'icon', cssPrefix = 'sit' } = this.options

    const generator = new LessGenerator({
      name: 'index',
      author,
      nameDisplayType: 'camel',
      description: '样式文件',
      prefix,
      cssPrefix,
    })

    return generator.process()
  }

  public getLessFile(): IIconFile {
    return {
      mime: 'text/css',
      path: 'runtime/index.less',
      content: this.getLessCode(),
    }
  }

  public getIndexFile(): IIconFile {
    return {
      mime: 'text/javascript',
      path: 'index.' + (this.options.useType ? 'ts' : 'js'),
      content: this.getIndexCode(),
    }
  }

  public getIndexCode(): string {
    const { author, useType = false } = this.options

    const generator = new IndexGenerator({
      name: 'index',
      author,
      nameDisplayType: 'camel',
      description: '引用出口',
      useType,
      icons: Object.keys(this.map),
    })

    return generator.process()
  }

  private createCompiler(): IconCompilerFunc {
    const {
      options: {
        type,
        colors = [],
        stroke = 0,
        fixedSize = false,
        style = false,
        strokeLinecap,
        strokeLinejoin,
      },
    } = this

    const hueList: IIconColorHueInfo[] = []

    const replaceList: IIconColorReplaceInfo[] = []

    colors.forEach((item) => {
      if (item.type === 'hue') {
        hueList.push(item)
      } else {
        replaceList.push(item)
      }
    })

    const isSvg = type === 'svg'

    return ({ name, content, description = name, rtl = false }) => {
      const plugins: ITransformPlugin[] = []

      // 删除无用的标签
      plugins.push(
        RemoveTagsTransformer({ tags: ['title', 'desc', 'a', 'metadata'] })
      )

      // 删除掉无用的属性（当生成目标是react时，xmlns无效）
      plugins.push(
        RemovePropsTransformer({
          props: ['version', 'xmlns:xlink'].concat(isSvg ? [] : ['xmlns']),
        })
      )

      // 修复mask-type属性的错误
      plugins.push(FixMaskTypeTransformer())

      // data开头的属性无用
      plugins.push(RemoveDataAttrTransformer())

      // 增加动态颜色替换，一定要在Hue替换之前
      if (replaceList.length) {
        plugins.push(
          DynamicColorTransformer({
            prefix: true,
            array: true,
            colors: replaceList.map((item) => item.color),
            ignore: (info) =>
              info.attrs.some(
                (item) =>
                  item.name === 'fill-opacity' && item.expression === '0.01'
              ),
          })
        )
      }

      // 增加动态颜色替换
      if (hueList.length) {
        plugins.push(
          DynamicHueTransformer({
            prefix: true,
            hueList: hueList.map((item) => item.hue),
            forceReplaceColor: false,
          })
        )
      }

      // 删除多余的ID
      plugins.push(UniqueIdTransformer({ prefix: true, removeUnusedIds: true }))

      // 处理CSS动画
      if (style) {
        plugins.push(UniqueKeyframesIdTransformer({ prefix: true }))
        plugins.push(RemoveCSSPrefixTransformer())
      }

      // size处理
      plugins.push(
        DynamicSizeTransformer({
          prefix: true,
          widthName: fixedSize ? 'size' : 'width',
          heightName: fixedSize ? 'size' : 'height',
        })
      )

      // 处理描边
      plugins.push(
        DynamicStrokeTransformer({
          prefix: true,
          disableStroke: !stroke,
          disableStrokeLinejoin: !strokeLinejoin,
          disableStrokeLinecap: !strokeLinecap,
        })
      )

      // 处理额外增加的Rect
      if (!isSvg) {
        plugins.push(
          RemoveConditionTransformer({
            tag: 'rect',
            condition: (info) =>
              info.attrs.some(
                (item) =>
                  item.name === 'fill-opacity' && item.expression === '0.01'
              ),
          })
        )
      }

      if (isSvg) {
        plugins.push(
          SyncGroupProps({
            attrNames: [
              'stroke-linecap',
              'stroke-linejoin',
              'stroke-width',
              'stroke',
              'fill',
              'fill-rule',
            ],
          })
        )
      }

      // React需要进行变量名升级
      if (!isSvg) {
        plugins.push(
          CamelTransformer({ namespace: true, namespaceOnly: type === 'vue' })
        )
      }

      let generator: IconGenerator =
        type === 'vue'
          ? this.createVueGenerator(name, description, rtl)
          : type === 'vue-next'
          ? this.createVueNextGenerator(name, description, rtl)
          : type === 'react'
          ? this.createReactGenerator(name, description, rtl)
          : this.createSvgGenerator(name, description)

      return compiler({
        content,
        plugins,
        generator,
      })
    }
  }

  private createSvgGenerator(name: string, description: string): SvgGenerator {
    const {
      author,
      style = false,
      prefix = 'icon',
      useType = false,
    } = this.options

    return new SvgGenerator({
      name,
      author,
      nameDisplayType: 'pascal',
      useDefault: true,
      useType,
      style,
      prefix,
      wrapperNeedName: true,
      wrapperNeedRTL: false,
      description,
      rtl: false,
      importPath: '../runtime',
    })
  }

  private createReactGenerator(
    name: string,
    description: string,
    rtl: boolean
  ): JSXGenerator {
    const {
      author,
      style = false,
      prefix = 'icon',
      useType = false,
    } = this.options

    return new JSXGenerator({
      name,
      author,
      rtl,
      nameDisplayType: 'pascal',
      useDefault: true,
      useType,
      style,
      prefix,
      wrapperNeedName: true,
      wrapperNeedRTL: true,
      extraImport: ["import React from 'react';"],
      description,
      importPath: '../runtime',
    })
  }

  private createVueGenerator(
    name: string,
    description: string,
    rtl: boolean
  ): JSXGenerator {
    const {
      author,
      style = false,
      prefix = 'icon',
      useType = false,
    } = this.options

    return new JSXGenerator({
      name,
      author,
      rtl,
      prefix,
      nameDisplayType: 'pascal',
      useDefault: true,
      useType,
      useHelper: true,
      wrapperNeedName: true,
      wrapperNeedRTL: true,
      style,
      description,
      importPath: '../runtime',
    })
  }

  private createVueNextGenerator(
    name: string,
    description: string,
    rtl: boolean
  ): JSXGenerator {
    const {
      author,
      style = false,
      prefix = 'icon',
      useType = false,
    } = this.options

    return new JSXGenerator({
      name,
      author,
      rtl,
      prefix,
      nameDisplayType: 'pascal',
      useDefault: true,
      useType,
      useHelper: false,
      wrapperNeedName: true,
      wrapperNeedRTL: true,
      style,
      description,
      importPath: '../runtime',
    })
  }

  private createRuntimeCode(): string {
    const {
      author,
      type,
      colors = [],
      prefix = 'icon',
      stroke = 0,
      theme = [],
      fixedSize = false,
      style = false,
      cssPrefix = 'sit',
      strokeLinecap,
      strokeLinejoin,
      useType = false,
    } = this.options

    const baseOptions: IRuntimeGeneratorOptions = {
      name: 'runtime',
      author,
      nameDisplayType: 'camel',
      description: '运行时',
      useType,
      colors,
      prefix,
      cssPrefix,
      stroke,
      theme,
      fixedSize,
      style,
      strokeLinecap,
      strokeLinejoin,
      wrapperNeedName: true,
    }

    const generator =
      type === 'vue'
        ? new VueRuntimeGenerator({
            ...baseOptions,
            wrapperNeedRTL: true,
          })
        : type === 'vue-next'
        ? new VueNextRuntimeGenerator({
            ...baseOptions,
            wrapperNeedRTL: true,
          })
        : type === 'react'
        ? new ReactRuntimeGenerator({
            ...baseOptions,
            wrapperNeedRTL: true,
          })
        : new SvgRuntimeGenerator({
            ...baseOptions,
          })

    return generator.process()
  }
}
